Laravel 提供了一整套身分驗證相關的功能,並且當我們安裝 Breeze 的時候就已經幫我們實作了大部分機能,不過為了客製或調整其中的功能,還是來了解一下運作的機制。
Auth Facade 幫助快速取用驗證的相關功能,包含登入登出等等。這邊先介紹直接使用這些功能的方法,底下再來解說背後的機制。
當使用者試圖登入的時候,在請求中帶上驗證身分所需的欄位值,在 Laravel 中預設的就是 email , password 。
/app/Http/Requests/Auth/LoginRequest.php
public function rules()
{
return [
'email' => ['required', 'string', 'email'],
'password' => ['required', 'string'],
];
}
這邊還沒介紹過資料檢查( Validation )的功能,不過上面的程式碼應該很好理解,email 跟 password 都是必須( required )欄位。
取得必須的欄位值之後就能用 Auth::attempt 進行檢查。
/app/Http/Requests/Auth/LoginRequest.php
use Illuminate\Support\Facades\Auth;
//...
public function authenticate()
{
$this->ensureIsNotRateLimited();
// 如果 attempt 檢查失敗的話就拋出錯誤
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'email' => __('auth.failed'),
]);
}
RateLimiter::clear($this->throttleKey());
}
Auth::attempt 的第一個參數是要驗證的資料陣列,寫得直接點的話會像
Auth::attempt([
'email' => 'your_email',
'password' => 'your_password'
]);
第二個參數則用於指定是否要記憶這個使用者,該值為 true 的話就記憶。
Auth::attempt([
'email' => 'your_email',
'password' => 'your_password'
],
$rememberMe
);
而記憶的方式則是在 user 的 remember_token 欄位存入 token ,之後使用者用網頁請求時就會用 cookie 內的資訊來對照這個 token 確認是否讓使用者快速登入。而當使用者登出的時候這個欄位就會清空,幫助防止 cookie 被劫持的安全問題 。
為了驗證身分所必須的欄位其實只有 password ,除 password 外添加的欄位都是用來檢索使用者,這些額外的欄位會應用於 where 的搜尋,只有被搜尋到的使用者會進行密碼驗證。
// 信箱相符且被啟用(active)的用戶才進行密碼驗證
if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
}
Auth::attempt 僅用於判定登入資訊是否正確,判定後是要導向首頁或是使用者原本嘗試拜訪的頁面、產生 session id 等行為都是另外定義的。
if (Auth::attempt(['email' => $email, 'password' => $password])) {
$request->session()->regenerate();
return redirect()->intended(RouteServiceProvider::HOME);
}
那如果使用者才剛申請完帳號怎麼辦呢? 如果是在已經有 User 實例的情況下,可以直接用 login 方法指定該 User 為目前登入的使用者,並更新 session 。
/app/Http/Controllers/Auth/RegisteredUserController.php
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
$user->setting()->create();
event(new Registered($user));
Auth::login($user); // 指定新申請的用戶為登入的 user
return redirect(RouteServiceProvider::HOME);
}
跟 attempt 一樣,可以指定是否記憶使用者
Auth::login($user,$remeberMe);
使用 Auth::logout() 能將更新 user session ,移除其中的登入資訊。
不過官方建議除了登出外也要完全清除 session 資訊,並且重產 csrf_token。
public function logout(Request $request)
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
Laravel 的身分驗證主要有兩個設定, guards 跟 providers
跟身分驗證有關的系統設定都在 config/auth.php 中。
首先可以找到 providers 的資料。
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
],
providers 可以理解為發行身分證的來源,主要就是指定要去哪裡取得身分相關的資料,像這邊定義 users 使用 eloquent 來查詢 App\Models\User 的資料,並利用查詢出的資料進行身分驗證。我們也可追加不同的身分發行類別。
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'admins' => [
'driver' => 'eloquent',
'model' => App\Models\Admin::class,
],
],
用在這邊的 model 需要經過特殊處理,需要有對應的欄位好進行身分驗證,最直接的方法就是繼承框架的 User Model
<?php
use Illuminate\Foundation\Auth\User as Authenticatable;
class Admin extends Authenticatable
{
//...
}
再打開 Illuminate\Foundation\Auth\User 可以看到也是很多介面或屬性的組合,可以參考來自製想要的 User Model ,比如說不想要信箱驗證功能的話,就不要引入 MustVerifyEmail 屬性。
/vendor/laravel/framework/src/Illuminate/Foundation/Auth/User.php
<?php
namespace Illuminate\Foundation\Auth;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\MustVerifyEmail;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\Access\Authorizable;
class User extends Model implements
AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
{
use Authenticatable, Authorizable, CanResetPassword, MustVerifyEmail;
}
guards 用於定義不同的"關口",各個關口會察看不同的身分證,或是用不同的方法來檢查身分證。
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],
像這邊定義了 web 這個關口,只檢查 providers 中的 users 身分證,並且用 session 的方式進行檢查。
檢查的方式有許多種,除了 session 外 Laravel 預設的套件中有 passport 跟 sanctum ,或是第三方套件的 jwt。
可以定義多個關口
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
當要進行身分檢查時,可以先指定要用哪個 guard 進行檢查,如果沒有指定就是使用 config/auth.php 設定的 default 值。
// 在 Admin 的資料中找的到的話才能驗證成功
Auth::guard('admin')->login($user);
Auth::guard('admin')->attempt($crednetials);
我們可以在路由中加上 auth 這個中介層,檢查使用者的身分,如果身分符合的話才放行。
/routes/auth.php
Route::post('/logout', [AuthenticatedSessionController::class, 'destroy'])
->middleware('auth')
->name('logout');
auth 只是個快捷鍵,在 app/Http/Kernel.php 中定義了 auth 對應的中介層類別。
app/Http/Kernel.php
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
//...
];
再仔細看看這個 Authenticate 類別,也只是繼承了框架底層的類別,詳細進行身分驗證的檢查都在 Illuminate\Auth\Middleware\Authenticate 中。
在專案的 app 目錄底下的 Authenticate 目前只用來指定當身分檢查失敗時要導向哪個頁面。
/app/Http/Middleware/Authenticate.php
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
class Authenticate extends Middleware
{
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login'); // 身分驗證失敗就導向登入頁面
}
}
}
當加入 auth 中介層時如果沒有額外指定,就是用預設的 guard 進行檢查。
要指定不同的 guard 話,加上 : 後指定
Route::get('/flights', function () {
// 通過 admin 檢查的用戶才能進入路由
})->middleware('auth:admin');
Laravel 預設的身分檢查是用基礎的 session-cookie 方式,如果想要改成其他方法的話可以利用套件。
這邊簡單介紹幾個可以用的套件,各自的安裝跟使用又都是大長篇就先不細說。
Laravel 預設包含的套件之一,可以用來進行 Outh2 驗證。
Laravel 預設包含的套件之一,以簡易的 token 進行驗證,可以應用於 SPA 跟行動裝置的 API 驗證。
顧名思義登入後就產出 jwt ,讓客戶端存好後使用。
幫 Model 產出 API key 並用來驗證,可以用於 S2S 的 API 驗證。